#include <Windows.h>
#include <stdio.h>

#include "BaseUtil.h"
#include "MacroUtil.h"
#include "StrUtil.h"

namespace str {

int Len(const char *s)
{
    return s ? (int)strlen(s) : 0;
}
int Len(const WCHAR *s)
{
    return s ? (int)wcslen(s) : 0;
}

char *Dup(const char *s)
{
    return s ? _strdup(s) : NULL;
}
WCHAR *Dup(const WCHAR *s)
{
    return s ? _wcsdup(s) : NULL;
}

bool Eq(const char *s1, const char *s2)
{
    if (s1 == s2)
        return true;
    if (!s1 || !s2)
        return false;
    return 0 == strcmp(s1, s2);
}

bool Eq(const WCHAR *s1, const WCHAR *s2)
{
    if (s1 == s2)
        return true;
    if (!s1 || !s2)
        return false;
    return 0 == wcscmp(s1, s2);
}

bool EqI(const char *s1, const char *s2)
{
    if (s1 == s2)
        return true;
    if (!s1 || !s2)
        return false;
    return 0 == _stricmp(s1, s2);
}

bool EqI(const WCHAR *s1, const WCHAR *s2)
{
    if (s1 == s2)
        return true;
    if (!s1 || !s2)
        return false;
    return 0 == _wcsicmp(s1, s2);
}

bool EqN(const char *s1, const char *s2, size_t len)
{
    if (s1 == s2)
        return true;
    if (!s1 || !s2)
        return false;
    return 0 == strncmp(s1, s2, len);
}

bool EqN(const WCHAR *s1, const WCHAR *s2, size_t len)
{
    if (s1 == s2)
        return true;
    if (!s1 || !s2)
        return false;
    return 0 == wcsncmp(s1, s2, len);
}

bool EqNI(const char *s1, const char *s2, size_t len)
{
    if (s1 == s2)
        return true;
    if (!s1 || !s2)
        return false;
    return 0 == _strnicmp(s1, s2, len);
}

bool EqNI(const WCHAR *s1, const WCHAR *s2, size_t len)
{
    if (s1 == s2)
        return true;
    if (!s1 || !s2)
        return false;
    return 0 == _wcsnicmp(s1, s2, len);
}

bool StartsWithI(const char *str, const char *txt)
{
    if (str == txt)
        return true;
    if (!str || !txt)
        return false;
    return 0 == _strnicmp(str, txt, str::Len(txt));
}

bool StartsWithI(const WCHAR *str, const WCHAR *txt)
{
    if (str == txt)
        return true;
    if (!str || !txt)
        return false;
    return 0 == _wcsnicmp(str, txt, str::Len(txt));
}

bool EndsWith(const char *txt, const char *end)
{
    if (!txt || !end)
        return false;
    size_t txtLen = str::Len(txt);
    size_t endLen = str::Len(end);
    if (endLen > txtLen)
        return false;
    return str::Eq(txt + txtLen - endLen, end);
}

bool EndsWith(const WCHAR *txt, const WCHAR *end)
{
    if (!txt || !end)
        return false;
    size_t txtLen = str::Len(txt);
    size_t endLen = str::Len(end);
    if (endLen > txtLen)
        return false;
    return str::Eq(txt + txtLen - endLen, end);
}

bool EndsWithI(const char *txt, const char *end)
{
    if (!txt || !end)
        return false;
    size_t txtLen = str::Len(txt);
    size_t endLen = str::Len(end);
    if (endLen > txtLen)
        return false;
    return str::EqI(txt + txtLen - endLen, end);
}

bool EndsWithI(const WCHAR *txt, const WCHAR *end)
{
    if (!txt || !end)
        return false;
    size_t txtLen = str::Len(txt);
    size_t endLen = str::Len(end);
    if (endLen > txtLen)
        return false;
    return str::EqI(txt + txtLen - endLen, end);
}


char *FmtV(const char *fmt, va_list args)
{
    char    message[256];
    size_t  bufCchSize = _countof(message);
    char  * buf = message;
    for (;;)
    {
        int count = _vsnprintf_s(buf, bufCchSize, _TRUNCATE, fmt, args);
        if ((count >= 0) && ((size_t)count < bufCchSize))
            break;
        /* we have to make the buffer bigger. The algorithm used to calculate
           the new size is arbitrary (aka. educated guess) */
        if (buf != message)
            free(buf);
        if (bufCchSize < 4*1024)
            bufCchSize += bufCchSize;
        else
            bufCchSize += 1024;
        buf = AllocArray<char>(bufCchSize);
        if (!buf)
            break;
    }

    if (buf == message)
        buf = str::Dup(message);

    return buf;
}

char *Format(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    char *res = FmtV(fmt, args);
    va_end(args);
    return res;
}

WCHAR *FmtV(const WCHAR *fmt, va_list args)
{
    WCHAR   message[256];
    size_t  bufCchSize = _countof(message);
    WCHAR * buf = message;
    for (;;)
    {
        int count = _vsnwprintf_s(buf, bufCchSize, _TRUNCATE, fmt, args);
        if ((count >= 0) && ((size_t)count < bufCchSize))
            break;
        /* we have to make the buffer bigger. The algorithm used to calculate
           the new size is arbitrary (aka. educated guess) */
        if (buf != message)
            free(buf);
        if (bufCchSize < 4*1024)
            bufCchSize += bufCchSize;
        else
            bufCchSize += 1024;
        buf = AllocArray<WCHAR>(bufCchSize);
        if (!buf)
            break;
    }
    if (buf == message)
        buf = str::Dup(message);

    return buf;
}

WCHAR *Format(const WCHAR *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    WCHAR *res = FmtV(fmt, args);
    va_end(args);
    return res;
}


/* Concatenate 2 strings. Any string can be NULL.
   Caller needs to free() memory. */
char *Join(const char *s1, const char *s2, const char *s3)
{
    if (!s1) s1= "";
    if (!s2) s2 = "";
    if (!s3) s3 = "";

    return Format("%s%s%s", s1, s2, s3);
}

/* Concatenate 2 strings. Any string can be NULL.
   Caller needs to free() memory. */
WCHAR *Join(const WCHAR *s1, const WCHAR *s2, const WCHAR *s3)
{
    if (!s1) s1 = L"";
    if (!s2) s2 = L"";
    if (!s3) s3 = L"";

    return Format(L"%s%s%s", s1, s2, s3);
}


/* Caller needs to free() the result */
char *ToMultiByte(const WCHAR *src, UINT codePage, int cchSrcLen)
{
    if (!src) return NULL;

    int requiredBufSize = WideCharToMultiByte(codePage, 0, src, cchSrcLen, NULL, 0, NULL, NULL);
    if (0 == requiredBufSize)
        return NULL;
    char *res = AllocArray<char>(requiredBufSize+1);
    if (!res)
        return NULL;
    WideCharToMultiByte(codePage, 0, src, cchSrcLen, res, requiredBufSize, NULL, NULL);
    return res;
}

/* Caller needs to free() the result */
WCHAR *ToWideChar(const char *src, UINT codePage, int cbSrcLen)
{
    if (!src) return NULL;

    int requiredBufSize = MultiByteToWideChar(codePage, 0, src, cbSrcLen, NULL, 0);
    if (0 == requiredBufSize)
        return NULL;
    WCHAR *res = AllocArray<WCHAR>(requiredBufSize+1);
    if (!res)
        return NULL;
    MultiByteToWideChar(codePage, 0, src, cbSrcLen, res, requiredBufSize);
    return res;
}

} // namespace str


/*
   00000000  30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46  0123456789ABCDEF\r\n
   00000010  41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50  ABCDEFGHIJKLMNOP\r\n
*/
char* MemToHexViewText(const unsigned char* pbyData, int nSize)
{
    int   nLineLen = 8 + 2 + 16 * 3 + 1 + 16 + 2;
    int   nTotalLines = 0;
    char* szHexViewText = NULL;
    int   nHexViewBufSize = 0;
    int   nHexViewLen = 0;

    nTotalLines = nSize / 16;
    if (nSize % 16)
        nTotalLines += 1;

    nHexViewBufSize = nTotalLines * nLineLen + 1;
    szHexViewText = AllocArray<char>(nHexViewBufSize);
    if (!szHexViewText)
        return NULL;

    for (int i = 0; i < nSize;)
    {
        // Address
        nHexViewLen += sprintf_s(szHexViewText + nHexViewLen, nHexViewBufSize - nHexViewLen, "%08X  ", i);

        char szHexBytes[16*3 + 1] = { 0 };    int nHexLen = 0;
        char szASCIIValue[16 + 1] = { 0 };    int nASCIILen = 0;
        do 
        {
            // Hex
            nHexLen += sprintf_s(szHexBytes + nHexLen, _countof(szHexBytes) - nHexLen, "%02X ", pbyData[i]);

            // ASCII
            if (isprint(pbyData[i]))
                nASCIILen += sprintf_s(szASCIIValue + nASCIILen, _countof(szASCIIValue) - nASCIILen, "%c", pbyData[i]);
            else
                nASCIILen += sprintf_s(szASCIIValue + nASCIILen, _countof(szASCIIValue) - nASCIILen, ".");
            i++;
        } while((i % 16 != 0) && (i < nSize));

        // A line
        nHexViewLen += sprintf_s(szHexViewText + nHexViewLen, nHexViewBufSize - nHexViewLen, "%-48s %s\r\n", szHexBytes, szASCIIValue);
    }

    return szHexViewText;
}

HRESULT MemToHexViewTextW(BYTE* pbyData, DWORD dwSize, WCHAR** ppwszHexViewText)
{
    HRESULT hResult = E_FAIL;

    //00000000  30 31 32 33 34 35 36 37 38 39 41 42 43 44 45 46  0123456789ABCDEF\r\n
    //00000010  41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50  ABCDEFGHIJKLMNOP\r\n
    DWORD  dwLineLen = 8 + 2 + 16 * 3 + 1 + 16 + 2;
    DWORD  dwTotalLines = 0;
    WCHAR* wszHexViewText = NULL;
    DWORD  dwHexViewBufSize = 0;
    DWORD  dwHexViewLen = 0;

    dwTotalLines = dwSize / 16;
    if (dwSize % 16)
        dwTotalLines += 1;

    dwHexViewBufSize = dwTotalLines * dwLineLen + 1;
    ALLOCATE_MEMORY(wszHexViewText, WCHAR, dwHexViewBufSize);

    for (DWORD i = 0; i < dwSize;)
    {
        // Address
        dwHexViewLen += swprintf_s(wszHexViewText + dwHexViewLen, dwHexViewBufSize - dwHexViewLen, L"%08X  ", i);

        WCHAR wszHexBytes[16*3 + 1] = { 0 };    DWORD dwHexLen = 0;
        WCHAR wszASCIIValue[16 + 1] = { 0 };    DWORD dwASCIILen = 0;
        do 
        {
            // Hex
            dwHexLen += swprintf_s(wszHexBytes + dwHexLen, _countof(wszHexBytes) - dwHexLen, L"%02X ", pbyData[i]);

            // ASCII
            if (isprint(pbyData[i]))
                dwASCIILen += swprintf_s(wszASCIIValue + dwASCIILen, _countof(wszASCIIValue) - dwASCIILen, L"%c", pbyData[i]);
            else
                dwASCIILen += swprintf_s(wszASCIIValue + dwASCIILen, _countof(wszASCIIValue) - dwASCIILen, L".");
            i++;
        } while((i % 16 != 0) && (i < dwSize));

        // A line
        dwHexViewLen += swprintf_s(wszHexViewText + dwHexViewLen, dwHexViewBufSize - dwHexViewLen, L"%-48ws %ws\r\n", wszHexBytes, wszASCIIValue);
    }

    *ppwszHexViewText = wszHexViewText;
    wszHexViewText = NULL;

    hResult = S_OK;

exit:
    FREE_MEMORY(wszHexViewText);

    return hResult;
}